package com.baofu.paycore.service.processor.refund;

import com.alibaba.dubbo.common.utils.CollectionUtils;
import com.baofu.clear.service.facade.enums.EventTypeEnum;
import com.baofu.paycore.biz.external.clear.EventService;
import com.baofu.paycore.biz.external.clear.model.EventRuleResBO;
import com.baofu.paycore.common.exception.PayCoreBizException;
import com.baofu.paycore.service.facade.enums.*;
import com.baofu.paycore.manager.model.*;
import com.baofu.paycore.manager.storage.impl.PayManager;
import com.baofu.paycore.manager.storage.impl.RefundStrategyManager;
import com.baofu.paycore.manager.storage.impl.SubPayManager;
import com.baofu.paycore.service.converter.BO2BOConverter;
import com.baofu.paycore.service.facade.enums.PayCoreErrorCode;
import com.baofu.paycore.service.facade.enums.PayStatusEnums;
import com.baofu.paycore.service.facade.enums.PayToolSubTypeEnum;
import com.baofu.paycore.service.facade.enums.PayToolTypeEnum;
import com.baofu.paycore.service.facade.model.refund.req.RefundReqDTO;
import com.system.commons.exception.BizServiceException;
import com.system.commons.sequence.redis.SequenceFacade;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 退款内部资金单构建处理器
 * <p>
 *     1、退款内部资金单对象构建
 * </p>
 * User: kwl Date: 2016/6/4 ProjectName: paycore Version: 5.0.0
 */
@Slf4j
@Service
public class RefundFundProcessor  {

    /**
     * 支付主单管理Manager
     */
    @Autowired
    private PayManager payManager;

    /**
     * 支付资金明细管理Manager
     */
    @Autowired
    private SubPayManager subPayManager;

    /**
     * 支付单号序列成工具类
     */
    @Autowired
    private SequenceFacade sequenceFacade;

    /**
     * 退款策略配置管理manager
     */
    @Autowired
    private RefundStrategyManager refundStrategyManager;

    /**
     * 会计事件服务
     */
    @Autowired
    private EventService eventService;

    /**
     * 余额支付退款资金单构建处理器
     */
    @Autowired
    private RefundAccountProcessor refundAccountProcessor;

    /**
     * 银行卡支付退款资金单构建处理器
     */
    @Autowired
    private RefundBankProcessor refundBankProcessor;


    /**
     * 退款内部资金单对象构建
     *
     * @param refundBO        支付模型
     * @param refundReqDTO    请求参数
     */
    public void process(RefundBO refundBO, RefundReqDTO refundReqDTO) {

        log.debug("退款内部资金单对象构建参数, refundBO:{}, refundReqDTO:{}",refundBO, refundReqDTO);

        //查询原单信息
        PayBO oldPayBO = refundBO.getOldPayBO();

        //退款策略
        List<RefundStrategyBO> list = refundStrategyManager.queryRefundProductStrategyMerge(
                oldPayBO.getBizType(), oldPayBO.getSubBizType());
        CollectionUtils.sort(list);

        //原交易资金单拆分
        Map<String,List<SubPayBO>> map = refundSplitByPayToolType(oldPayBO.getSubPayList());

        //退款流程
        subRefundProcess(refundBO, map, list);

        log.debug("退款内部资金单对象构建结果, refundBO:{}",refundBO);

        //更新原支付单退款金额
        modifyPayOrderRefundAmount(refundBO, oldPayBO);
    }

    /**
     * 退款交易流程-原交易资金单拆分
     * <p>
     * 1、正向支付时，根据支付工具细类对支付资金单进行拆分，即安支付工具区分资金单。如：
     * 银行卡快捷支付的时，会同时生成充值转账单（对应银行卡充值流程）、资金账户转账单。
     * 在退款时，这2笔资金单需要绑定一起退款。将原交易按照 “支付工具细类” 进行拆分分组
     *
     * 2、拆分结果是一个map集合，其中：
     * map key：支付工具细类类型，
     * map value：按 key 拆分后的资金单信息集合
     *
     * 注意：目前不考虑退款收手续费,若收手续费，需指定手续费承担方
     * </p>
     *
     * @param subPayBOs    原支付资金单信息
     * @return             原交易资金单拆分后信息
     */
    private Map<String,List<SubPayBO>> refundSplitByPayToolType(List<SubPayBO> subPayBOs){

        //拆分后资金单
        Map<String,List<SubPayBO>> map = new HashMap<>();

        //原资金单拆分
        for(PayToolSubTypeEnum payToolSubTypeEnum : PayToolSubTypeEnum.values()) {

            List<SubPayBO> splitBOs = subPayBOs.stream().filter(subPayBO -> StringUtils.equals(
                    payToolSubTypeEnum.getSubPayToolCode(), subPayBO.getClassType())).collect(Collectors.toList());

            //原单分类
            if(!CollectionUtils.isEmpty(splitBOs)) {
                map.put(payToolSubTypeEnum.getSubPayToolCode(), splitBOs);
            }
        }
        if(map.size() == 0) {
            log.error("退款失败, 退款原单解析异常。 error:{}", PayCoreErrorCode.ORIG_ORDER_DETAIL_ANALYSIS_ERROR);
            throw new BizServiceException(PayCoreErrorCode.ORIG_ORDER_DETAIL_ANALYSIS_ERROR);
        }
        return map;
    }

    /**
     * 资金单构建
     *
     * <p>
     * 资金单构建，根据拆分后的原资金单进行判断，若拆分后的资金单只有一份，则走单支付工具流程，
     * 若资金单多于1份，则走混合支付工具流程。若拆分后的资金单为空，则退款异常，资金单构建失败。
     * </p>
     *
     * @param refundBO            退款订单信息
     * @param map                 原交易资金单拆分后信息
     * @param list                退款策略信息
     */
    private void subRefundProcess(RefundBO refundBO, Map<String,List<SubPayBO>> map, List<RefundStrategyBO> list){

        //退款流程-退款资金单构建
        for(RefundStrategyBO refundStrategyBO : list){

            PayToolSubTypeEnum payToolSubTypeEnum = PayToolSubTypeEnum.explain(refundStrategyBO.getPayToolType());

            if(null == payToolSubTypeEnum) {
                log.error("退款策略配置错误,支付工具配置错误 error:{}", PayCoreErrorCode.REFUND_STRATEGY_CONFIG_ERROR);
                throw new PayCoreBizException(PayCoreErrorCode.REFUND_STRATEGY_CONFIG_ERROR);
            }

            List<SubPayBO> splitBOs = map.get(payToolSubTypeEnum.getSubPayToolCode());

            if(CollectionUtils.isEmpty(splitBOs)) {
                continue;
            }

            //外部资金源退款流程
            if(PayToolRouteEnum.CLEAR_WEBPAY_ROUTE.containsPayTool(payToolSubTypeEnum.getPayToolCode())) {

                refundBankProcessor.process(refundBO, splitBOs, refundStrategyBO.getRefundTargetMode());
            }

            //余额账户退款流程
            if(PayToolRouteEnum.ACCOUNT_TRANSFER_ROUTE.containsPayTool(payToolSubTypeEnum.getPayToolCode())) {

                refundAccountProcessor.process(refundBO, splitBOs);
            }
        }

        if(CollectionUtils.isEmpty(refundBO.getSubRefundBOs())) {
            log.error("退款资金单构建失败, 退款资金单明细为空");
            throw new BizServiceException(PayCoreErrorCode.REFUND_ORDER_DETAIL_BUILD_ERROR);
        }

        //退款流程- 收“手续费” 资金单构建
        /*if(0 != refundBO.getFeeAmt()){
            List<SubRefundBO> feeBOs = feeAmtProcess(refundBO);
            refundBO.getSubRefundBOs().addAll(feeBOs);
        }*/
    }

    /**
     * 退款业务收手续费流程
     *
     * @param refundBO            退款主单
     * @return subRefundBOs       退款资金单集合
     */
    private List<SubRefundBO> feeAmtProcess(RefundBO refundBO){

        List<SubRefundBO> subRefundBOs = new ArrayList<>();

        SubRefundBO subRefundBO = new SubRefundBO();
        subRefundBO.setFundDetailNo(sequenceFacade.getUniqueSeq());
        subRefundBO.setPayToolType(PayToolTypeEnum.ACCOUNT.getCode());
        subRefundBO.setAmt(refundBO.getFeeAmt());

        //退款业务手续费收取，由原支付交易的出账账户出
        subRefundBO.setPayerAccNo(refundBO.getFeePayerAccNo());

        EventRuleResBO ruleResBO = eventService.queryEventNo(EventTypeEnum.FEE, refundBO.getSubBizType());

        if (!PayStatusEnums.SUCCESS.getCode().equals(ruleResBO.getStatus())) {
            log.error("产品大类:{}, 产品细类:{} ", refundBO.getBizType(),
                    refundBO.getSubBizType());
            throw new BizServiceException(PayCoreErrorCode.EVENT_NO_NOT_EXIST);
        }

        subRefundBO.setEventNo(ruleResBO.getEventNo());
        subRefundBOs.add(BO2BOConverter.getSubRefundBOByFeeAmt(refundBO, subRefundBO));

        return subRefundBOs;
    }

    /**
     * 原支付单退款金额更新
     *
     * @param payBO    原支付单信息
     */
    private void modifyPayOrderRefundAmount(RefundBO refundBO, PayBO payBO){

        log.debug("更新原支付单退款金额, refundBO:{}, payBO:{}", refundBO, payBO);

        //设置原支付主单退款金额
        payBO.setRefundTradeAmt(refundBO.getAmt() - refundBO.getFeeAmt());
        payBO.setRefundAmt(payBO.getRefundAmt() + refundBO.getAmt() - refundBO.getFeeAmt());

        //原支付主单更新
        payManager.modifyRefundAmt(payBO);

        //原支付资金明细单更新
        payBO.getSubPayList().forEach(subPayManager::modifyRefundAmt);
    }

}
